package com.weixin.servlet;

import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import com.weixin.beaninfo.TextMSG;
import com.weixin.util.AccessTokenUtils;
import com.weixin.util.AnalyzeXmlUtil;
import com.weixin.util.CheckUtil;
import com.weixin.util.JSONUtils;
import com.weixin.util.ReadyProcessUtils;

import net.sf.json.JSONObject;

public class RelateServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private static Document ServiceOpinion=null;	//service.xml文件document对象
	private static Document ExchangeField=null;//exchange.xml文件的document对象
	private static String AccessTokenPath=null;//保存token的文件
	private static String ObjectPath=null;//保存document对象的的文件
	/**
	 * servlet初始化，解析service.xml文件。
	 */
	public void init(ServletConfig config) throws ServletException {
		SAXReader reader=new SAXReader();
		SAXReader reader2=new SAXReader();
		super.init(config);
		String ServiceOpinion_path=getServletContext().getRealPath("")+"\\WEB-INF\\classes\\service.xml".replace("\\", "/");
		String ExchangeField_path=getServletContext().getRealPath("")+"\\WEB-INF\\classes\\exchange.xml".replace("\\", "/");
		try {
			InputStream in=new FileInputStream(new File(ServiceOpinion_path));
			ServiceOpinion=reader.read(in);
			InputStream in2=new FileInputStream(new File(ExchangeField_path));
			ExchangeField=reader2.read(in2);
			in.close();
			in2.close();
		} catch (Exception e) {
			e.printStackTrace();
		} 
	}
	/**
	 * 验证接入公众号接口是否成功
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//这里用于连入微信公众号接口
		String signature=request.getParameter("signature");
		String timestamp=request.getParameter("timestamp");
		String nonce=request.getParameter("nonce");
		String echostr=request.getParameter("echostr");
		PrintWriter out =response.getWriter();
		if(CheckUtil.checkPW(timestamp, nonce, signature)){
			//获取AccessToken
			String url="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
			AccessTokenPath=request.getRealPath("");
			ObjectPath=request.getRealPath("");
			AccessTokenUtils.ifGetJson(url, AccessTokenPath);
			out.print(echostr);
		}
	}
	/**
	 * 信息接收post，并执行相应的业务
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
		/**
		 * 接收业务
		 */
		//获取request中的xml字符串
		Document RequestDocument=AnalyzeXmlUtil.getDocument(request);
		//RequestDoument添加服务节点<ServiceType>
		PrintWriter out =response.getWriter();//将信息发送到微信公众号的工具类，信息发送也是使用xml格式
		//解析接收的信息
//		printXml(excute(RequestDocument));
		/**
		 * 发送业务
		 */
		//返回地图信息
		StringBuffer content=new StringBuffer();
		doService(RequestDocument,content);
		TextMSG text=getTextMSG(RequestDocument,content.toString());
//		//创建发送的Text信息
//		System.out.println(text.getContent());
		Document replyDocument=AnalyzeXmlUtil.getDocumentByObj(text,ObjectPath);
		//发送信息
		out.print(excute(replyDocument));
		
	}
	/**
	 * 	为了将公众号传递过来的信息进行分类，并解析service.xml文件，获取相应业务的类名，方法名，反射调用执行。
	 * 		1.分为普通信息和事件两大类
	 * 		2.将上面两个大类分为:
	 * 					1）普通信息：文本信息，图片信息，语音信息，视频消息，地理信息，小视频信息，链接信息。
	 * 					2）事件信息：关注/取消关注事件，扫描带参数二维码事件，上报地理位置事件，自定义菜单事件点击。
	 * @param 业务处理返回信息
	 */
	String excute(Document RequestDocument){
		if(ServiceOpinion==null)
			return null;
		if(RequestDocument==null)
			return null;
		//用于存放属性值
		Map<String,Object> attrs=new HashMap<String,Object>();
		//获取用户输入参数
		RequestDocument=indentifyService(RequestDocument);
		String RequestEvent=AnalyzeXmlUtil.getTextByName(RequestDocument, "Event");
		String RequestMsgType=AnalyzeXmlUtil.getTextByName(RequestDocument, "MsgType");
		//确定对service.xml匹配的属性值，确定服务业务
		attrs.put("event", RequestEvent);
		attrs.put("MsgType", RequestMsgType);
		attrs.put("ServiceType", RequestDocument.getRootElement().element("ServiceType").getText());
		//将<ServiceType节点删除>
		RequestDocument=indentifyService(RequestDocument);
		//进行对service.xml匹配，找到符合条件的Elment元素，根据MsgType和event两个节点值进行查找
		Element serviceElement=AnalyzeXmlUtil.getElementByAttr(ServiceOpinion, attrs);
		//此处开始进入反射调用
		String className=serviceElement.attributeValue("className");
		String methodName=serviceElement.attributeValue("methodName");
		try {
			Class clazz=Class.forName(className);
			Method method=clazz.getMethod(methodName, Document.class);
			Object obj=method.invoke(clazz.newInstance(), RequestDocument);
			if(obj instanceof String){
				return String.valueOf(obj);
			}
			String xmlMessage=AnalyzeXmlUtil.getXMLByObject(obj);
			return xmlMessage;
		} catch (Exception e) {
			System.out.println("根据类名"+className+"反射创建类失败。");
		}
		return null;
	}
	/**
	 * 将xml字符串转化具有格式的字符串并打印出来
	 * @param xmlMessage xml字符串
	 */
	void printXml(String xmlMessage){
		for(int i=0;i<xmlMessage.length()-1;i++){
			System.out.print(xmlMessage.charAt(i));
			if(i==xmlMessage.length()-2)
				System.out.print(xmlMessage.charAt(i+1));
			if(xmlMessage.charAt(i)=='>'&&xmlMessage.charAt(i+1)=='<'){
				System.out.println();
			}
		}
		System.out.println();
	}
	/**
	 * 自动进行添加删除ServiceType节点，通过识别id,确定是接受服务还是发送服务
	 * @param  document 对document进行修饰，添加删除Service节点
	 */
	Document indentifyService(Document document){
		String value=AnalyzeXmlUtil.getTextByName(document, "MsgId");
//		System.out.println("MsgId:"+value); 测试数据
		Map<String,Object> attr=new HashMap<String,Object>();
		if(value==null||value.equals("")){
			attr.put("ServiceType", "send");
			AnalyzeXmlUtil.changeElement(document, attr);
		}
		else{
			attr.put("ServiceType", "receive");
			AnalyzeXmlUtil.changeElement(document, attr);
		}
		return document;
	}
	/**
	 * 本方法用于将封装了数据的对象转成具有一定格式的字符串数据，发送给公众号的信息。
	 * @param obj
	 */
	String sendText(Object obj,StringBuffer content){
		//通过匹配exchange.xml文件，查找到相应的服务，对字段名进行加工。
		Map<String,Object> attr=new HashMap<String,Object>();
		if(obj==null)
			return content.append("未查找到相应的服务，请重新输入地区或者服务").toString();
		attr.put("Beantype", obj.getClass().getName());
		Element exchange=AnalyzeXmlUtil.getElementByAttr(ExchangeField,attr);
		//需要进行加工的字段
		String[] processFields=exchange.attributeValue("fields").split(",");
		//加工字段所需的数据
		String[] processValues=exchange.attributeValue("values").split(",");
		Class clazz=obj.getClass();
		Field[] fields=clazz.getDeclaredFields();
		for (Field field : fields) {
			String fieldName=field.getName();
			//如果遍历的对象字段不是processFields数组中的值，则不添加至content。
			for (int i=0;i<processFields.length;i++) {
				if(fieldName.equals(processFields[i])){
					try {
						PropertyDescriptor pd=new PropertyDescriptor(fieldName, clazz);
						Method method=pd.getReadMethod();
						Object value=method.invoke(obj);
						if(value==null){
							value="无";
						}
						//将符合要求的字段进行加工。
						if(processValues[i].equals("无"))
							content.append("");
						else
							content.append(processValues[i]).append(":");
						//添加字段的value
						content.append(value).append("\n");
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		}
		return content.toString();
	}
	/**
	 * 创建发送给用户的文本消息类
	 * @param RequestDocument
	 * @param content
	 * @return
	 */
	TextMSG getTextMSG(Document RequestDocument,String content){
		//创建发送的Text信息
		TextMSG replyText=new TextMSG();
		replyText.setCreateTime("2016-11-09");
		replyText.setFromUserName(AnalyzeXmlUtil.getTextByName(RequestDocument, "ToUserName"));
		replyText.setContent(content.toString());
		replyText.setMsgId("");
		replyText.setMsgType("text");
		replyText.setToUserName(AnalyzeXmlUtil.getTextByName(RequestDocument, "FromUserName"));
		return replyText;
	}
	/**
	 * 解析用户输入的内容，获取服务类型，相关搜索参数，调用相应的业务并执行。
	 * @param document 用户输入的内容
	 * @return
	 */
	boolean doService(Document document,StringBuffer values){
		String[] condition=ReadyProcessUtils.deleteEnter(document.getRootElement().element("Content").getText()).split(" ");//条件
		//将不符合条件的参数输入进行判断
		if(condition.length!=3){
			values.append("输入参数格式有误，请按照正确格式(服务编号 服务参数1 服务参数2)重新输入");
			return false;
		}
		String ServiceName=condition[0];
//		System.out.println(ServiceName);
		document=indentifyService(document);
		//在匹配相应得节点（匹配service.xml节点）
		Map<String,Object> attrs=new HashMap<String,Object>();
		//在匹配相应得节点（匹配exchange.xml节点）
		Map<String,Object> attrs2=new HashMap<String,Object>();
		//获取用户输入参数
		String RequestEvent=AnalyzeXmlUtil.getTextByName(document, "Event");
		String RequestMsgType=AnalyzeXmlUtil.getTextByName(document, "MsgType");
		//确定对service.xml匹配的属性值，确定服务业务
		attrs.put("event", RequestEvent);
		attrs.put("MsgType", RequestMsgType);
		attrs.put("ServiceType", document.getRootElement().element("ServiceType").getText());
		attrs.put("ServiceName", ServiceName);
		//进行对service.xml匹配，找到符合条件的Elment元素，根据MsgType和event两个节点值进行查找
		//删除节点
		document=indentifyService(document);
		Element serviceElement=AnalyzeXmlUtil.getElementByAttr(ServiceOpinion, attrs);
		//匹配对应的exhange.xml节点
		attrs2.put("id",serviceElement.attributeValue("id"));
		Element exchangeElement=AnalyzeXmlUtil.getElementByAttr(ExchangeField, attrs2);
		JSONObject jsondata=null;
		String className=serviceElement.attributeValue("className");//业务类名字
		String BeanName=exchangeElement.attributeValue("Beantype");//实体类名
		Class clazz=null;
		try {
		    //进行反射调用
			clazz = Class.forName(className);
			Method method=clazz.getMethod(serviceElement.attributeValue("methodName"), Document.class);
			Object o=method.invoke(clazz.newInstance(), document);
			jsondata=JSONObject.fromObject(o);
			if(jsondata.get("message").equals("failed")){
				values.append(jsondata.get("results"));
				return false;
			}
		} catch (Exception e) {
				e.printStackTrace();
				return false;
		}
		List<Object> jsonList=JSONUtils.getLocationByPlace(jsondata, BeanName);
		if(jsonList.get(0)!=null){
			for (Object object : jsonList) {
				sendText(object,values);
				values.append("\n");
			}
		}
		else{
			sendText(null,values);
		}
		return true;
	}
}